package com.lemon.exam.common.config;

import com.lemon.exam.common.authentication.GlobalAuthorityAuthorizationManager;
import com.lemon.exam.common.authentication.LoginAuthenticationManager;
import com.lemon.exam.common.constant.RedisKeyConst;
import com.lemon.exam.common.entity.system.LoginUser;
import com.lemon.exam.common.filter.JwtAuthenticationFilter;
import com.lemon.exam.common.filter.LoginWebFilter;
import com.lemon.exam.common.filter.RefreshWebFilter;
import com.lemon.exam.common.handler.PermissionEvaluatorHandler;
import com.lemon.exam.common.properties.ConfigProperties;
import com.lemon.exam.common.redis.RedisService;
import com.lemon.exam.common.util.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;

/**
 * Security 配置类，配置权限规则，退出规则，异常处理器，CORS 跨域等
 *
 * @author Lemon
 * @since 2025/3/20 12:10
 */
@Slf4j
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity(proxyTargetClass = true) //启用响应式方法级安全
@AutoConfigureBefore({ConfigProperties.class, JwtAuthenticationFilter.class, LoginAuthenticationManager.class, LoginWebFilter.class, RefreshWebFilter.class, RedisService.class, RequestMappingHandlerMapping.class, PermissionEvaluatorHandler.class})
public class SecurityConfiguration {

    /**
     * 配置认证，授权规则
     *
     * @param http
     * @param configProperties
     * @param jwtAuthenticationFilter
     * @param loginAuthenticationManager
     * @param loginFilter
     * @param refreshFilter
     * @param redisService
     * @param requestMappingHandlerMapping
     * @return
     */
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, ConfigProperties configProperties, JwtAuthenticationFilter jwtAuthenticationFilter, LoginAuthenticationManager loginAuthenticationManager, LoginWebFilter loginFilter, RefreshWebFilter refreshFilter, RedisService redisService, RequestMappingHandlerMapping requestMappingHandlerMapping) {
        return http
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)//禁用表单登录
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())//配置会话管理为无状态
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)//禁用 BASIC 认证
                .csrf(ServerHttpSecurity.CsrfSpec::disable)//禁用 CSRF (前后端分离中不需要跨站请求伪造，使用Token认证)
                .cors(corsSpecCustomizer(configProperties))//配置 CORS 跨域
                .headers(headersSpec -> headersSpec.frameOptions(ServerHttpSecurity.HeaderSpec.FrameOptionsSpec::disable))//禁用 X-Frame-Options 防止iframe 跨域
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())//配置会话管理为无状态
                .authorizeExchange(authorizeExchangeSpecCustomizer(configProperties, requestMappingHandlerMapping))//配置权限规则
                .logout(logoutSpecCustomizer(configProperties, redisService))//配置退出规则
                .authenticationManager(loginAuthenticationManager)//配置认证管理器
                .exceptionHandling(exceptionHandlingSpecCustomizer())//配置异常处理器
                .addFilterAt(loginFilter, SecurityWebFiltersOrder.FORM_LOGIN)//添加登录过滤器
                .addFilterAt(refreshFilter, SecurityWebFiltersOrder.AUTHENTICATION)//添加刷新过滤器
                .addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)//添加 JWT 过滤器
                .build();
    }

    /**
     * 配置权限规则
     *
     * @return
     */
    @Bean("globalMethodSecurityExpressionHandler")
    @Primary
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluatorHandler permissionEvaluatorHandler) {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(permissionEvaluatorHandler);
        return handler;
    }

    /**
     * 配置 CORS 跨域
     *
     * @param configProperties
     * @return
     */
    private static Customizer<ServerHttpSecurity.CorsSpec> corsSpecCustomizer(ConfigProperties configProperties) {
        var allowed = configProperties.getAllowed();
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(allowed.getOrigins()); //允许请求来源
        configuration.setAllowedMethods(allowed.getMethods()); //允许请求方法
        configuration.setAllowedHeaders(allowed.getHeaders()); //允许请求头
        configuration.setAllowCredentials(allowed.getCredential()); //允许携带凭证
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);//对所有的请求进行配置
        return cors -> cors.configurationSource(source);
    }

    /**
     * 配置权限规则
     *
     * @return
     */
    private static Customizer<ServerHttpSecurity.AuthorizeExchangeSpec> authorizeExchangeSpecCustomizer(ConfigProperties configProperties, RequestMappingHandlerMapping requestMappingHandlerMapping) {
        return exchanges -> exchanges
                .pathMatchers(configProperties.getKnife4jIgnoreUriArray()).permitAll()//允许访问 Knife4j 静态资源
                .pathMatchers(configProperties.getSystemIgnoreUriArray()).permitAll()//允许访问系统白名单
                .anyExchange().access(new GlobalAuthorityAuthorizationManager(requestMappingHandlerMapping));//使用自定义的管理器
    }

    /**
     * 配置退出
     *
     * @return
     */
    private static Customizer<ServerHttpSecurity.LogoutSpec> logoutSpecCustomizer(ConfigProperties configProperties, RedisService redisService) {
        return logoutSpec -> logoutSpec
                .logoutUrl(configProperties.getJwt().getLogoutPath())
                .logoutSuccessHandler((exchange, authentication) -> {
                    LoginUser loginUser = (LoginUser) authentication.getPrincipal();
                    String username = loginUser.getUsername();
                    log.info("{},正在退出....", username);
                    return redisService.hash().del(RedisKeyConst.LOGIN_USER, username)
                            .flatMap(status -> {
                                log.info("{},退出成功！redis.status:{}", username, status);
                                authentication.setAuthenticated(false);
                                return ResponseUtil.ok(exchange.getExchange().getResponse(), "退出成功！");
                            });
                });
    }

    /**
     * 配置异常处理器
     *
     * @return
     */
    private static Customizer<ServerHttpSecurity.ExceptionHandlingSpec> exceptionHandlingSpecCustomizer() {
        return exceptionHandlingSpec -> exceptionHandlingSpec
                .accessDeniedHandler((exchange, exception) -> {
                    log.error("权限访问被拒绝，也就是未授权访问的接口:{}，message:{}", exchange.getRequest().getPath().value(), exception.getMessage());
                    return ResponseUtil.err(exchange.getResponse(), HttpStatus.UNAUTHORIZED.value(), "未授权访问！");
                });
    }
}
